#include <iostream>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

// mmap:映射文件或设备到内存中,在调用者进程的虚拟地址空间那里创建新的映射
// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
// 返回值：成功->返回被映射到的那块区域的地址；失败->返回(void*)-1
// 参数：addr->被映射区域的起始地址，设为NULL时内核自动选择；length->文件的大小；
//       prot->protection设置内存保护权限，如PROT_READ\PROT_WRITE；flags->能否被映射到同一块区域的其他进程看到
//       fd->要映射的文件的文件描述符；offset->文件的读写位置偏移量
// 映射的内容不是覆盖，而是从头开始映射；映射到内存中修改后即便关闭了映射，文件的内容也被修改了
int main()
{
    const char *filename = "log.txt";
    const char *text = "hello mmap! This is a test file.";
    struct stat file_buffer;

    // 1.打开文件，获取文件描述符
    int fd = ::open(filename, O_RDWR | O_CREAT, 0666);
    if (fd == -1)
    {
        std::cerr << "open error" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 2.写入初始内容
    ::write(fd, text, strlen(text));

    // 3.获取文件大小
    if (fstat(fd, &file_buffer) == -1)
    {
        std::cerr << "fstat error" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 4.将文件映射到内存(进程的虚拟地址空间)
    char *mmapped = (char *)mmap(NULL, file_buffer.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mmapped == MAP_FAILED)
    {
        std::cerr << "mmap error" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 5.通过内存映射读取文件内容
    std::cout << "File content via mmap:" << mmapped << std::endl;

    // 6.通过内容映射修改文件内容
    std::cout << "Modifying file via mmap..." << std::endl;
    strncpy(mmapped + 6, "MMAP", 4);
    std::cout << "File content via mmap:" << mmapped << std::endl;

    // 7.解除内存映射
    if (munmap(mmapped, file_buffer.st_size) == -1)
    {
        std::cerr << "munmap error" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 8.关闭文件
    ::close(fd);

    // 9.再次打开文件验证更改
    std::cout << "Verifying changes by reading file directly:" << std::endl;
    FILE *fp = fopen(filename, "r");
    if (fp)
    {
        char ch;
        while ((ch = fgetc(fp)) != EOF)
        {
            putchar(ch);
        }
        fclose(fp);
    }

    // 10.删除文件
    ::unlink(filename);
    return 0;
}